﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Windows.Forms;
using System.Data.

namespace RouteValidator
{
    class Validator
    {
        //General Purpose class for Running the Validation Routines
        //Stores a copy of all XML-Nodes in Lists for faster sorting and inspection


        public int ErrorCount = 0;//Running total of Critical validation issues (Errors)
        public int WarningCount = 0;//Running total of non-critical validation inconsistencies (Warnings)

        XmlTextReader routeSourceXml = null;
        ListBox outputListBox = null;
        
        List<Path> pathList = new List<Path>();
        List<Node> nodeList = new List<Node>();
        List<Link> linkList = new List<Link>();
        Route routeData = new Route();        

        string xmlPath;

        public Validator(string path, ListBox list)
        {
            //Accepts location of the source XML file and a reference to output WinForm Control (a listbox)
            xmlPath = path;            
            outputListBox = list;

            //Run Validation routines in most efficient order
            LoadRouteXML();
            if (IDsUnique() && LinksIntegral())
                CheckIDIntegrity();
            else
                LogWarning("Validation cannot proceed with outstanding Errors, please correct");

            Console.Beep();
        }
        

        private void LoadRouteXML() //Scans XML file and adds data entities to their own list
        {
            
            //TODO: make XML reading safe
            routeSourceXml = new XmlTextReader(xmlPath);              

            while (routeSourceXml.Read())
            {
                switch (routeSourceXml.Name)
                {
                    case "Route":
                        LoadRoute();
                        break;
                    case "Path":
                        AddPath();
                        break;
                    case "Node":
                        AddNode();
                        break;
                    case "Link":
                        AddLink();
                        break;
                    case "":
                    case "/Route":
                    case "xml":
                        //Ignore
                        break;
                    default:
                        LogWarning("Unknown XML node: " + routeSourceXml.Name);
                        break;
                }
            }
            //Sort Lists by their 'unique' attributes
            pathList.Sort(delegate(Path p1, Path p2) { return p1.Order.CompareTo(p2.Order); });
            linkList.Sort(delegate(Link l1, Link l2) { return l1.LinkID.CompareTo(l2.LinkID); });
            nodeList.Sort(delegate(Node n1, Node n2) { return n1.NodeID.CompareTo(n2.NodeID); });
            
            routeSourceXml.Close();
        }

        private bool IDsUnique() //Ensures that any Data attributes that should be unique, are.
        {                        //TODO: Prime candidate for use of Generics
            bool isValid = true;
            
            for (int i = 1; i < pathList.Count; i++)
            {
                if (pathList[i].Order > pathList[i - 1].Order)
                    continue;
                LogError("Path with order " + pathList[i].Order.ToString() + " appears more than once");
                isValid = false;
            }

            
            for (int i = 1; i < linkList.Count; i++)
            {
                if (linkList[i].LinkID > linkList[i - 1].LinkID)
                    continue;
                LogError("Link with ID " + linkList[i].LinkID.ToString() + " appears more than once");
                isValid = false;
            }

            
            for (int i = 1; i < nodeList.Count; i++)
            {
                if (nodeList[i].NodeID > nodeList[i - 1].NodeID)
                    continue;

                LogError("Node with ID " + nodeList[i].NodeID.ToString() + " appears more than once");
                isValid = false;
            }

            return isValid;
        }

        private bool LinksIntegral() //Ensures that all Nodes are connected in the order specified by the Paths,
        {                            //and checks that first and last node reflect 'isCircular' characteristic
            
            bool isValid = true;

            for (int i = 1; i < pathList.Count; i++)
            {
                if (pathList[i].StartNode != pathList[i - 1].EndNode)
                {
                    LogError("Consecutive paths " + pathList[i-1].Order.ToString() + " and " + pathList[i].Order.ToString() + " do not connect");
                    isValid = false;
                }
            }

            if ((pathList[pathList.Count - 1].EndNode != pathList[0].StartNode) && routeData.IsCircular)
            {
                LogError("Very first and very last nodes do not connect on circular route");
            }
            return isValid;
        }

        private void CheckIDIntegrity()//Flags any nodes that aren't properly defined or aren't actually included in Route
        {
            List<int> uniqueNodeIDs = new List<int>();

            //Add all NodeID Path attributes to a list and sort by ascending
            foreach (Path path in pathList)
            {
                uniqueNodeIDs.Add(path.StartNode);
                uniqueNodeIDs.Add(path.EndNode);
            }
            uniqueNodeIDs.Sort(delegate(int i1, int i2) { return i1.CompareTo(i2); });

            //Remove all duplicates from list
            for (int i = 1; i < uniqueNodeIDs.Count; )
            {
                if (uniqueNodeIDs[i] == uniqueNodeIDs[i - 1])
                    uniqueNodeIDs.RemoveAt(i);
                else
                    i++;                
            }

            //If a NodeID is defined but not used, flag a warning, otherwise, remove the Node from memory
            foreach (Node node in nodeList)
            {
                if (uniqueNodeIDs.Contains(node.NodeID))
                    uniqueNodeIDs.Remove(node.NodeID);
                else
                    LogWarning("Node " + node.NodeID.ToString() + " is defined but not used in Route Description");
            }

            //If there are still Node IDs left in memory, they can't have been defined, so flag an error
            foreach (int id in uniqueNodeIDs)
                LogError("Node " + id.ToString() + " is referenced but not defined");

        }

        private void LoadRoute()//Loads Route Characteristics, can only be set by the first 'Route' Element in XML file
        {
            if (routeData.set) return;
            
            int b = 0;
            if (!int.TryParse(routeSourceXml.GetAttribute("isCircular"), out b))
                LogWarning("Route is not specified as circular, default is FALSE");
            else
                routeData.IsCircular = (b > 0) ? true : false;

            routeData.Name = routeSourceXml.GetAttribute("name");
            routeData.UID = routeSourceXml.GetAttribute("unique_id");

            if (routeData.UID == null || routeData.Name == null)
                LogError("Neither Route Name nor Unique ID can be null");

            routeData.set = true;
        }

        private void AddNode()//Parses Node information from XML and Handles invalid/null attributes
        {
            Node node = new Node();

            routeSourceXml.MoveToFirstAttribute();
            do
            {
                if (routeSourceXml.AttributeCount == 6)
                {
                    if (!node.Evaluate(routeSourceXml.LocalName, routeSourceXml.Value))
                        LogError("Invalid field in Node at line " + routeSourceXml.LineNumber.ToString());
                }
                else
                    LogWarning("Attribute Count Mismatch in Node at line " + routeSourceXml.LineNumber.ToString() + ", check XML format");

            } while (routeSourceXml.MoveToNextAttribute());

            nodeList.Add(node);
        }

        private void AddLink()//Parses Link information from XML and Handles invalid/null attributes
        {
            Link link = new Link();

            routeSourceXml.MoveToFirstAttribute();
            do
            {
                if (routeSourceXml.AttributeCount == 4)
                {
                    if (!link.Evaluate(routeSourceXml.LocalName, routeSourceXml.Value))
                        LogError("Invalid field in Link at line " + routeSourceXml.LineNumber.ToString());
                }
                else
                    LogWarning("Attribute Count Mismatch in Link at line " + routeSourceXml.LineNumber.ToString() + ", check XML format");

            } while (routeSourceXml.MoveToNextAttribute());

            linkList.Add(link);
        }

        private void AddPath()//Parses Path information from XML and Handles invalid attributes
        {
            Path path = new Path();

            routeSourceXml.MoveToFirstAttribute();
            do
            {
                int parseResult;

                //Check Validity of each field
                if (int.TryParse(routeSourceXml.Value, out parseResult))
                {
                    //Add current attribute to Path container
                    //Flag a warning if the number of attributes doesn't match
                    if (routeSourceXml.AttributeCount == 4)
                        path.Evaluate(routeSourceXml.LocalName, parseResult);
                    else
                        LogWarning("Attribute Count Mismatch in path at line " + routeSourceXml.LineNumber.ToString() + ", check XML format");
                }
                else
                    LogError("Invalid field in path at line " + routeSourceXml.LineNumber.ToString());
                
            } while (routeSourceXml.MoveToNextAttribute());

            pathList.Add(path);
        }

        private void LogError(string message)
        {
            ErrorCount++;
            outputListBox.Items.Add("Error: " + message);
        }

        private void LogWarning(string message)
        {
            WarningCount++;
            outputListBox.Items.Add("Warning: " + message);
        }
            
    }
}
